const isStringArrayContentEqual =
  (arr1?: string[] | undefined | null, arr2?: string[] | undefined | null): boolean => {
    if (typeof arr1 === 'undefined' || typeof arr2 === 'undefined' || arr1 === null || arr2 == null || !arr1 ||
      !arr2) {
      return false;
    }
    if (arr1.length === 0 && arr2.length === 0) {
      return true;
    }
    if (arr1.length !== arr2.length) {
      return false;
    }

    // 排序并比较
    const sortedArr1 = arr1.slice().sort(); // 使用 slice() 复制数组以避免改变原数组
    const sortedArr2 = arr2.slice().sort();
    return sortedArr1.every((value, index) => value === sortedArr2[index]);
  }

export type LimitedKeys<T> = keyof T;

const stringToNumber = (value?: string | number | any, defaultValue?: number): number | undefined => {
  return numberOrNull(value) ?? defaultValue;
};

const arrayFilterToEnd = (array: any[], filter: (item: any) => boolean): any[] => {
  if (!array || array.length <= 0 || !filter) {
    return array;
  }
  // @ts-ignore
  const start = [];
  // @ts-ignore
  const end = [];
  array.forEach(item => {
    if (!filter(item)) {
      start.push(item);
    } else {
      end.push(item);
    }
  });
  // @ts-ignore
  return start.concat(end);
};
const isValid = (value?: any): boolean => {
  return (
    (typeof value === 'number' && !isNaN(value) && isFinite(value)) ||
      (typeof value === 'string' && value?.length && value !== '--') ||
      (typeof value !== 'number' && typeof value !== 'string' && typeof value !== 'undefined' && value !== null)
  );
};

/**
 * check object is '{ }'
 * @param object
 */
const isObjectEmpty = (object?: any): boolean => {
  return !object || Object.keys(object).length === 0;
};

/**
 * 通过四舍五入法保留 decimals 位小数 https://stackoverflow.com/a/32178833/4348530
 * @param value
 * @param decimals
 */
const round = (value: number, decimals: number = 0): number => {
  if (typeof value === 'undefined' || typeof value !== 'number' || typeof decimals !== 'number') {
    return value;
  }
  return Number(Math.round(parseFloat(value + 'e' + decimals)) + 'e-' + decimals);
};

/**
 * 通过四舍五入法保留 decimals 位小数, 补0
 * @param value
 * @param decimals
 * @return null if not a number
 */
const roundString = (value: number | undefined | string, decimals: number = 0): string | any => {
  if (
    typeof value === 'undefined' ||
      (typeof value !== 'number' && typeof value !== 'string') ||
      typeof decimals !== 'number' ||
      (typeof value === 'string' && !isValidNumber(value))
  ) {
    return value;
  }
  return round(typeof value === 'string' ? +value : value, decimals).toFixed(decimals);
};

/**
 * 输入时格式化, 非四舍五入, 直接对字符串进行截断, 替换
 * 1. 只能输入浮点数(数字和'.')
 * 2. 只能输入 decimals 个小数
 *
 * @param value input string value
 * @param decimals >=0
 * @return format string
 */
const inputNumberFormat = (value: string, decimals: number = 1): string => {
  decimals = decimals < 0 ? 0 : decimals;
  return (
  value
    .replace(/[^\d.]/g, '')// 将所有非数字替换为空, '/' 表示正则分隔符, '[]' 表示字符集合, '^' 中括号内为取反中括号外为开头/起始符, '^\d'. 非数字, '/g' 全局匹配
    .replace(/\.{2,}/g, '.')// 连续2个点替换为1个点
      //region 保证整个字符串只有一个'.', 把'.'转换成一个比较特殊的字符串以防止被第二个正则替换给替换掉
    .replace('.', '$#$')// 只将第一个字符'.'替换成特殊字符串'$#$', 利用 replace 对于字符串只匹配一次
    .replace(/\./g, '')// 将其余的字符'.'删除
    .replace('$#$', decimals <= 0 ? '' : '.')// 把特殊字符串'$#$'替换回原来的'.', 如果不需要小数这里不再添加'.'
      //endregion
      // .replace(/^(-)*(\d+)\.(\d).*$/, "$1$2.$3") // 保留1位小数, 第一组重复匹配0-n个'-'; 第二组匹配1-n个数字; 随后匹配一个'.'; 第三组匹配一个数字; 随后匹配任意字符0-n个直到结束(.*$); 替换为'(第一组匹配)(第二组匹配).(第三组匹配)'
    .replace(
      new RegExp(
        '^(-)*(\\d+)\\.(' +
        new Array(decimals)
          .fill(0)
          .map(() => '\\d')
          .join('') +
          ').*$',
      ),
      '$1$2.$3',
    )// 正则 '//' 作为正则分隔符, 使用new RegExp()动态生成时不需要加这两个斜杠, 但是对'\d'这里的'\'要加多一个'\'进行转义
    .replace(/^\./g, '') // 如果开头是'.'则删除
  );
};

/**
 * 千 兆 吉 太 拍, 最小千 最大拍
 * @param value
 * @return {
 *   value: number,
 *   unit: string // 'k'/'M'/'G'/'T'/'P'
 * }
 */
const short = (value: number): { value: number; unit: string } => {
  if (typeof value === 'undefined' || typeof value !== 'number' || isNaN(value)) {
    return {
      value: value,
      unit: '',
    };
  }

  const isNegative = value < 0;
  const absValue = Math.abs(value);

  if (absValue < 1000) {
    return { value: value, unit: '' };
  }
  const kValue = absValue / 1000;
  if (kValue < 1000) {
    return { value: isNegative ? -kValue : kValue, unit: 'k' };
  }
  const MValue = kValue / 1000;
  if (MValue < 1000) {
    return { value: isNegative ? -MValue : MValue, unit: 'M' };
  }
  const GValue = MValue / 1000;
  if (GValue < 1000) {
    return { value: isNegative ? -GValue : GValue, unit: 'G' };
  }
  const TValue = GValue / 1000;
  if (TValue < 1000) {
    return { value: isNegative ? -TValue : TValue, unit: 'T' };
  }
  const PValue = TValue / 1000;
  return { value: isNegative ? -PValue : PValue, unit: 'P' };
};

/**
 * @param value
 * @param decimals
 * @return null if not a number
 */
const shortRoundString = (value: number, decimals?: number): { value: string; unit: string } => {
  const shortValue = short(value);
  return {
    value: roundString(shortValue.value, decimals),
    unit: shortValue.unit,
  };
};

/**
 * 千 兆 吉 太 拍, 最小千 最大拍
 * @param kValue
 * @return {
 *   value: number,
 *   unit: string // 'k'/'M'/'G'/'T'/'P'
 * }
 */
const kShort = (kValue: number | undefined): { value?: number; unit: string } => {
  if (typeof kValue === 'undefined' || typeof kValue !== 'number' || isNaN(kValue)) {
    return {
      value: kValue,
      unit: '',
    };
  }

  const isNegative = kValue < 0;
  const absValue = Math.abs(kValue);

  if (absValue < 1000) {
    return { value: kValue, unit: 'k' };
  }
  const MValue = absValue / 1000;
  if (MValue < 1000) {
    return { value: isNegative ? -MValue : MValue, unit: 'M' };
  }
  const GValue = MValue / 1000;
  if (GValue < 1000) {
    return { value: isNegative ? -GValue : GValue, unit: 'G' };
  }
  const TValue = GValue / 1000;
  if (TValue < 1000) {
    return { value: isNegative ? -TValue : TValue, unit: 'T' };
  }
  const PValue = TValue / 1000;
  return { value: isNegative ? -PValue : PValue, unit: 'P' };
};

/**
 * @param kValue
 * @param decimals
 * @return null if not a number
 */
const kShortRoundString = (kValue: number | undefined, decimals?: number): { value?: string; unit: string } => {
  const kShortValue = kShort(kValue);
  return {
    value: roundString(kShortValue.value, decimals),
    unit: kShortValue.unit,
  };
};

const isValidNumber = (value: number | string | null | undefined): boolean => {
  return numberOrNull(value) !== null;
};

/**
 * https://i.stack.imgur.com/QNMfI.png
 * @param value
 */
const numberOrNull = (value: number | string | null | undefined) => {
  if (typeof value === 'undefined' || value === null) {
    return null;
  }
  if (typeof value === 'string') {
    value = +value;
  }
  if (isNaN(value) || !isFinite(value)) {
    return null;
  }
  return value;
};

const stringOrNull = (value: number | string | null | undefined) => {
  if (typeof value === 'undefined' || value === null) {
    return null;
  }
  if (typeof value === 'number') {
    if (!isNaN(value) && isFinite(value)) {
      value = `${value}`;
    } else {
      return null;
    }
  }
  if (!value?.length) {
    return null;
  }
  return value;
};

/**
 * @param value
 * @return not empty string or null
 */
const stringOrEmptyToNull = (value: string | null | undefined): string | null => {
  return value?.length ? value : null;
};

/**
 * @param text
 * @return 0 or else
 */
const hashCode = (text: string | null | undefined): number => {
  let hash = 0;
  let i;
  let chr;
  if (!text?.length) {
    return hash;
  }
  for (i = 0; i < text.length; i++) {
    chr = text.charCodeAt(i);
    // eslint-disable-next-line no-bitwise
    hash = (hash << 5) - hash + chr;
    // eslint-disable-next-line no-bitwise
    hash |= 0; // Convert to 32bit integer
  }
  return hash;
};
const hashCodeString = (text: string | null | undefined): string => {
  return `${hashCode(text)}`;
};

/**
 * @param array
 * @param index
 * @return nonnull copied array
 */
function moveToFirst<T>(array: Array<T> | null | undefined, index: number): Array<T> {
  if (!array?.length) {
    return [];
  }
  const copiedArray = [...array];
  if (index < 0 || index >= copiedArray.length) {
    return copiedArray;
  }
  const target = copiedArray[index];
  if (index >= 0) {
    copiedArray.splice(index, 1);
    copiedArray.unshift(target);
  }
  return copiedArray;
}

function stringContainsNumbers(str: string | null | undefined) {
  return str?.length && /[0-9]/.test(str); // /\d/ === /[0-9]/
}

function stringContainsOnlyNumbers(str: string | null | undefined) {
  return str?.length && /^[0-9]+$/.test(str);
}

function stringContainsOnlyLetters(str: string | null | undefined) {
  return str?.length && /^[A-Za-z]+$/.test(str);
}

function stringContainsOnlyLettersOrNumbers(str: string | null | undefined) {
  return str?.length && /^[A-Za-z0-9]+$/.test(str);
}

/**
 * 判断是否同时包含字母和数字, 不允许包含其他字符
 * @param str
 */
function stringContainsOnlyLettersAndNumbers(str: string | null | undefined) {
  return str?.length && /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]*$/.test(str);
}

/**
 * 判断是否同时包含字母和数字, 允许包含其他字符
 * @param str
 */
function stringContainsLetterAndNumbers(str: string | null | undefined) {
  return str?.length && /^(?=.*[a-zA-Z])(?=.*\d).+$/.test(str);
}

function phoneWithoutCountry(phone: string | null | undefined, country?: string | null | undefined): string {
  if (!phone?.length) {
    return '';
  }
  let account = phone.replaceAll('+', '').replaceAll(' ', '').trim();
  let tmpCountry = country?.replaceAll('+', '')?.replaceAll(' ', '').trim() ?? '86';
  if (account.startsWith(tmpCountry)) {
    account = account.replace(tmpCountry, '');
  }
  return account;
}

function phoneWithSpace(phone: string | null | undefined): string {
  if (!phone?.length) {
    return '';
  }
  // 去除空格
  let phoneNumber = phone.replaceAll(' ', '').trim();
  // 去除非数字字符
  phoneNumber = phoneNumber.replace(/\D/g, '');

  // 计算格式
  const firstSegment = 3;
  const otherSegments = 4;

  // 格式化
  let formatted = '';
  for (let i = 0; i < phoneNumber.length; i++) {
    formatted += phoneNumber[i];

    if (i === firstSegment - 1 && i !== phoneNumber.length - 1) {
      formatted += ' ';
    } else if ((i - firstSegment + 1) % otherSegments === 0 && i !== phoneNumber.length - 1) {
      formatted += ' ';
    }
  }
  return formatted;
}

const checkEmail = (email: string | undefined | null) => {
  return email?.trim()?.length && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim());
};

const checkPhone = (phone: string | undefined | null) => {
  return phone?.trim()?.length && /^\+?[0-9]{1,3}-?[0-9]{3,14}$/.test(phone.trim());
};

const randomColor = (alpha?: number | undefined): string => {
  const r = Math.floor(Math.random() * 256);
  const g = Math.floor(Math.random() * 256);
  const b = Math.floor(Math.random() * 256);
  return `rgba(${r}, ${g}, ${b}, ${alpha ?? 1})`;
}


const ValueUtil = {
  randomColor,
  isStringArrayContentEqual,
  checkEmail,
  checkPhone,
  isValidNumber,
  phoneWithSpace,
  phoneWithoutCountry,
  numberOrNull,
  stringOrNull,
  stringOrEmptyToNull,
  stringContainsNumbers,
  stringContainsOnlyNumbers,
  stringContainsOnlyLetters,
  stringContainsOnlyLettersOrNumbers,
  stringContainsOnlyLettersAndNumbers,
  stringContainsLetterAndNumbers,
  moveToFirst,
  hashCode,
  hashCodeString,
  stringToNumber: stringToNumber,
  arrayFilterToEnd: arrayFilterToEnd,
  isObjectEmpty: isObjectEmpty,
  isValid: isValid,
  round: round,
  roundString: roundString,
  inputNumberFormat: inputNumberFormat,
  short: short,
  shortRoundString: shortRoundString,
  kShort: kShort,
  kShortRoundString: kShortRoundString,
};

export default ValueUtil;
